use chrono::{DateTime, Utc};
use play_api::*;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, time::Duration};

//MOJANG MENIFEST JSON
#[derive(Deserialize, Serialize, Clone, Debug)]
struct RootManifest {
    latest: HashMap<String, String>,
    versions: Vec<MinecraftVersion>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct MinecraftVersion {
    pub id: String,
    #[serde(rename = "type")]
    pub type_: String,
    pub url: String,
    #[serde(rename = "releaseTime")]
    pub release_time: DateTime<Utc>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct VersionManifest {
    #[serde(rename = "assetIndex")]
    pub asset_index: AssetIndex,
    pub downloads: HashMap<String, Client>,
    pub libraries: Vec<Library>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Library {
    pub downloads: Download,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Artifact {
    pub path: String,
    pub sha1: String,
    pub size: u64,
    pub url: String,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Download {
    pub artifact: Artifact,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Client {
    pub sha1: String,
    pub size: u64,
    pub url: String,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct AssetIndex {
    pub id: String,
    pub url: String,
    pub sha1: String,
    pub size: u64,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct AssetManifest {
    pub objects: HashMap<String, Asset>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Asset {
    pub hash: String,
    pub size: u64,
}
fn default_client() -> reqwest::Result<reqwest::Client> {
    reqwest::ClientBuilder::new()
        .timeout(Duration::from_secs(10))
        .build()
}
pub struct MinecraftDownloader;
impl GetVersion for MinecraftDownloader {
    type Error = reqwest::Error;
    const ASSET_URL: &str = "https://resources.download.minecraft.net/";
    const ROOT_URL: &str = "https://piston-meta.mojang.com/mc/game/version_manifest.json";
    async fn get_version_list(&self) -> Result<Vec<MCVersion>, Self::Error> {
        let agent = default_client()?;
        let response = agent.get(Self::ROOT_URL).send().await?;
        let json: RootManifest = response.json().await?;
        let versions = json
            .versions
            .iter()
            .map(|obj| MCVersion {
                version: obj.id.clone(),
                type_: obj.type_.clone(),
                time: obj.release_time,
                url: obj.url.clone(),
            })
            .collect();
        Ok(versions)
    }

    async fn get_file_list(&self, version: MCVersion) -> Result<Vec<GameFile>, Self::Error> {
        let agent = default_client()?;
        let response = agent.get(&version.url).send().await?;
        let json: VersionManifest = response.json().await?;
        let mut files = Vec::new();
        //CLIENT
        let c = &json.downloads["client"];
        files.push(GameFile {
            path: format!(".minecraft/versions/{0}/{0}.jar", version.version),
            size: c.size,
            sha1: c.sha1.clone(),
            url: c.url.clone(),
        });

        //LIBRARIES
        json.libraries.iter().for_each(|f| {
            let a = &f.downloads.artifact;
            files.push(GameFile {
                path: format!(".minecraft/libraries/{}", a.path),
                size: a.size,
                sha1: a.sha1.clone(),
                url: a.url.clone(),
            })
        });
        //INDEX
        files.push(GameFile {
            path: format!(".minecraft/assets/indexes/{}.json", json.asset_index.id),
            size: json.asset_index.size,
            sha1: json.asset_index.sha1,
            url: json.asset_index.url.clone(),
        });
        let response = agent.get(&json.asset_index.url).send().await?;
        let json: AssetManifest = response.json().await?;
        //OBJECTS
        json.objects.iter().for_each(|(_k, v)| {
            let hash_path = format!("{}/{}", &v.hash[0..2], v.hash);
            files.push(GameFile {
                path: format!(".minecraft/assets/objects/{}", hash_path),
                size: v.size,
                sha1: v.hash.clone(),
                url: format!("{}/{}", Self::ASSET_URL, hash_path),
            })
        });
        Ok(files)
    }
}
